﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

using Microsoft.Xna.Framework;

using Microsoft.Xna.Framework.Graphics;

using FXnaEngine.Graphics;

using System.IO;



namespace FXnaEngine
{

    /// <summary>
    /// 用于表示基本的地形结构,提供多样化的数据载入体系
    /// </summary>
    public class FTerrain:FMeshProvider
    {


        #region 构造函数



        /// <summary>
        /// 默认情况下应该构造一个平面地形
        /// </summary>
        /// <param name="width"></param>
        /// <param name="height"></param>
        public FTerrain(int width, int height)
        {

            this.gridWidth = width;

            this.gridHeight = height;


            mapHeights = new float[GridWidth, GridHeight];




            //这里为了测试，暂时使用文件地形




            string fullPath = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "Content\\LandscapeHeights.data");


            FileStream file = File.Open(fullPath,
                   FileMode.Open, FileAccess.Read, FileShare.ReadWrite);


            byte[] heights = new byte[GridWidth * GridHeight];
            file.Read(heights, 0, GridWidth * GridHeight);
            file.Close();


          

            for (int x = 0; x < GridWidth; x++)
            {
                for (int y = 0; y < GridHeight; y++)
                {

                       // Get height
                     float heightPercent = heights[x + y * GridWidth] / 255.0f;


                    //这里提前计算好地形高度，提高算法查询效率

                     mapHeights[x, y] = heightPercent*mapZScale;




                }
            }



            //计算最大最小值

            this.CalMinMax();
          



 
        }



        /// <summary>
        /// 提供基于数据的地形数据
        /// </summary>
        /// <param name="width"></param>
        /// <param name="height"></param>
        /// <param name="data"></param>
        public FTerrain(int width, int height, float[,] data)
        {


 
        }


        #endregion





        /// <summary>
        /// Map heights
        /// </summary>
        float[,] mapHeights = null;



        float maxHeight=float.MinValue;

        public float MaxHeight
        {
            get { return maxHeight; }
            set { maxHeight = value; }
        }




        float minHeight=float.MaxValue;

        public float MinHeight
        {
            get { return minHeight; }
            set { minHeight = value; }
        }




        int gridWidth = 257;

        public int GridWidth
        {
            get { return gridWidth; }
           
        }


        int gridHeight = 257;

        public int GridHeight
        {
            get { return gridHeight; }
           
        }



        float mapWidthFactor = 1;

        public float MapWidthFactor
        {
            get { return mapWidthFactor; }
            set { mapWidthFactor = value; }
        }

        float mapHeightFactor = 1;

        public float MapHeightFactor
        {
            get { return mapHeightFactor; }
            set { mapHeightFactor = value; }
        }

        float mapZScale = 30;

        public float MapZScale
        {
            get { return mapZScale; }
            set { 
                
                mapZScale = value; 


            
            
            }
        }





        public float WidthGeoLength
        {

            get {

                return this.mapWidthFactor * this.gridWidth;


            }
             
        }

        public float HeightGeoLength
        {

            get
            {

                return this.mapHeightFactor * this.gridHeight;


            }
             


        }







        #region 方法

        public override void BuildMesh()
        {



            TangentVertex[] vertices = new TangentVertex[GridWidth * GridHeight];




            #region Build tangent vertices
            // Build our tangent vertices
            for (int x = 0; x < GridWidth; x++)
                for (int y = 0; y < GridHeight; y++)
                {

                    // Step 1: Calculate position
                    int index = x + y * GridWidth;
                    Vector3 pos = CalcPos(x, y);//texData);
                
                    vertices[index].pos = pos;





                    //if (x == 0)
                    //    Log.Write("vertices " + y + ": " + pos);

                    // Step 2: Calculate all edge vectors (for normals and tangents)
                    // This involves quite complicated optimizations and mathematics,
                    // hard to explain with just a comment. Read my book :D
                    Vector3 edge1 = pos - CalcPos(x, y + 1);
                    Vector3 edge2 = pos - CalcPos(x + 1, y);
                    Vector3 edge3 = pos - CalcPos(x - 1, y + 1);
                    Vector3 edge4 = pos - CalcPos(x + 1, y + 1);
                    Vector3 edge5 = pos - CalcPos(x - 1, y - 1);


                    // Step 3: Calculate normal based on the edges (interpolate
                    // from 3 cross products we build from our edges).
                    vertices[index].normal = Vector3.Normalize(
                        Vector3.Cross(edge2, edge1) +
                        Vector3.Cross(edge4, edge3) +
                        Vector3.Cross(edge3, edge5));

                    // Step 4: Set tangent data, just use edge1
                    vertices[index].tangent = Vector3.Normalize(edge1);

                    // Step 5: Set texture coordinates, use full 0.0f to 1.0f range!
                    vertices[index].uv = new Vector2(
                        //x / (float)(GridWidth - 1),
                        //y / (float)(GridHeight - 1));
                        y / (float)(GridHeight - 1),
                        x / (float)(GridWidth - 1));
                }
            #endregion

            #region Smooth normals
            // Smooth all normals, first copy them over, then smooth everything
            Vector3[,] normalsForSmoothing = new Vector3[GridWidth, GridHeight];
            for (int x = 0; x < GridWidth; x++)
                for (int y = 0; y < GridHeight; y++)
                {
                    int index = x + y * GridWidth;
                    normalsForSmoothing[x, y] = vertices[index].normal;
                }

            // Time to smooth to normals we just saved
            for (int x = 1; x < GridWidth - 1; x++)
                for (int y = 1; y < GridHeight - 1; y++)
                {
                    int index = x + y * GridWidth;

                    // Smooth 3x3 normals, but still use old normal to 40% (5 of 13)
                    Vector3 normal = vertices[index].normal * 4;
                    for (int xAdd = -1; xAdd <= 1; xAdd++)
                        for (int yAdd = -1; yAdd <= 1; yAdd++)
                            normal += normalsForSmoothing[x + xAdd, y + yAdd];
                    vertices[index].normal = Vector3.Normalize(normal);

                    // Also recalculate tangent to let it stay 90 degrees on the normal
                    Vector3 helperVector = Vector3.Cross(
                        vertices[index].normal,
                        vertices[index].tangent);
                    vertices[index].tangent = Vector3.Cross(
                        helperVector,
                        vertices[index].normal);
                }
            #endregion


            #region Set vertex buffer
            // Set vertex buffer
            // fix
            //vertexBuffer = new VertexBuffer(
            //    BaseGame.Device,
            //    typeof(TangentVertex),
            //    vertices.Length,
            //    ResourceUsage.WriteOnly,
            //    ResourceManagementMode.Automatic);
            //vertexBuffer.SetData(vertices);
            VertexBuffer vertexBuffer = new VertexBuffer(
                 FEngineState.Device,
                 typeof(TangentVertex),
                 vertices.Length,
                 BufferUsage.WriteOnly);
            vertexBuffer.SetData(vertices);

            #endregion


            #region Calc index buffer
            // Calc index buffer (Note: have to use uint, ushort is not sufficiant
            // in our case because we have MANY vertices ^^)
            uint[] indices = new uint[(GridWidth - 1) * (GridHeight - 1) * 6];
            int currentIndex = 0;
            for (int x = 0; x < GridWidth - 1; x++)
                for (int y = 0; y < GridHeight - 1; y++)
                {
                    // Set landscape data (Note: Right handed)
                    indices[currentIndex + 0] = (uint)(x * GridHeight + y);
                    indices[currentIndex + 2] =
                        (uint)((x + 1) * GridHeight + (y + 1));
                    indices[currentIndex + 1] = (uint)((x + 1) * GridHeight + y);
                    indices[currentIndex + 3] =
                        (uint)((x + 1) * GridHeight + (y + 1));
                    indices[currentIndex + 5] = (uint)(x * GridHeight + y);
                    indices[currentIndex + 4] = (uint)(x * GridHeight + (y + 1));

                    // Add indices
                    currentIndex += 6;
                }
            #endregion


            #region Set index buffer
            // fix
            //indexBuffer = new IndexBuffer(
            //    BaseGame.Device,
            //    typeof(uint),
            //    (GridWidth - 1) * (GridHeight - 1) * 6,
            //    ResourceUsage.WriteOnly,
            //    ResourceManagementMode.Automatic);

            IndexBuffer indexBuffer = new IndexBuffer(
                 FEngineState.Device,
                 typeof(uint),
                 (GridWidth - 1) * (GridHeight - 1) * 6,
                 BufferUsage.WriteOnly);

            indexBuffer.SetData(indices);
            #endregion







            InnerMesh.VertexBuffer = vertexBuffer;
            InnerMesh.IndexBuffer = indexBuffer;

            InnerMesh.VertexDeclaration = TangentVertex.VertexDeclaration;

            InnerMesh.PrimitiveType = PrimitiveType.TriangleList;

            InnerMesh.NumberOfVertices = vertices.Length;

            InnerMesh.NumberOfPrimitives = (GridWidth - 1) * (GridHeight - 1) * 2;



      




           



        }




        /// <summary>
        /// 计算单元格点的实际坐标位置
        /// 
        /// </summary>
        /// <param name="x">X</param>
        /// <param name="y">Y</param>
        /// <returns>Vector 3</returns>
        public Vector3 CalcPos(int x, int y)
        {
            // Make sure we stay on the valid map data
            int mapX = x < 0 ? 0 : x >= GridWidth ? GridWidth - 1 : x;
            int mapY = y < 0 ? 0 : y >= GridHeight ? GridHeight - 1 : y;

           

            // Build landscape position vector

            //让俺们搞个小测试,重新更正数据位Y向上，考虑到诸多的因素，比如光照，XNA底层等

            //return new Vector3(
            //    mapX * MapWidthFactor,
            //    mapY * MapHeightFactor,
            //    this.mapHeights[mapX, mapY]);

            return new Vector3(mapX * MapWidthFactor,this.mapHeights[mapX, mapY],mapY * MapHeightFactor);

          

        }











        /// <summary>
        /// Get map height at a specific point
        /// </summary>
        /// <param name="x">X</param>
        /// <param name="y">Y</param>
        /// <returns>Float</returns>
        public float GetMapHeight(float x, float y)
        {
            // Rescale to our current dimensions
            x /= MapWidthFactor;
            y /= MapHeightFactor;

            // Interpolate the current position
            int
                // size-1 is because we need +1 for interpolating
                ix = ModulateValueInRange(x, GridWidth - 1),
                iy = ModulateValueInRange(y, GridHeight - 1);

            // Get the position ON the current tile (0.0-1.0)!!!
            float
                fX = x - ((float)((int)x)),
                fY = y - ((float)((int)y));

            int ix2 = (ix + 1) % (GridWidth - 1);
            int iy2 = (iy + 1) % (GridHeight - 1);

            if (fX + fY < 1) // opt. version
            {
                // we are on triangle 1 !!
                //     ------- (f_tile_width-mx)/f_tile_width
                //  0__v___1
                //  |     /
                //  |    /
                //  |---/--- (f_tile_height-my)/f_tile_height
                //  |  /
                //  | /
                //  3/
                return
                    mapHeights[ix, iy] + // 0
                    fX * (mapHeights[ix2, iy] - mapHeights[ix, iy]) + // 1
                    fY * (mapHeights[ix, iy2] - mapHeights[ix, iy]); // 3
            }
            // we are on triangle 1 !!
            // calc height (as above, but a bit more difficult for triangle 1)
            //        1
            //       /|
            //      / |
            //     /  |  my/f_tile_height (fX)
            //    /   |
            //   /    |
            //  3_____2
            //     ^---  mx/f_tile_width  (fY)
            return
                mapHeights[ix2, iy2] + // 2
                (1.0f - fY) * (mapHeights[ix2, iy] - mapHeights[ix2, iy2]) +    // 1
                (1.0f - fX) * (mapHeights[ix, iy2] - mapHeights[ix2, iy2]); // 3
        }

        /// <summary>
        /// Get map height at a specific point, int based and not as percise as
        /// the float version, which interpolates between our grid points.
        /// </summary>
        /// <param name="x">X</param>
        /// <param name="y">Y</param>
        /// <returns>Float</returns>
        public float GetMapHeight(int x, int y)
        {
            if (x < 0)
                x = 0;
            if (y < 0)
                y = 0;
            if (x >= GridWidth)
                x = GridWidth - 1;
            if (y >= GridHeight)
                y = GridHeight - 1;

            return mapHeights[x, y];
        }


        /// <summary>
        /// This functions keeps sure we keep in 0-max range,
        /// simple modulate (%) will do this only correctly for positiv values!
        /// </summary>
        private static int ModulateValueInRange(float val, int max)
        {
            if (val < 0.0f)
                return (max - 1) - ((int)(-val) % max);
            else
                return (int)val % max;
        }




        protected void CalMinMax()
        {


            for (int x = 0; x < GridWidth; x++)
            {
                for (int y = 0; y < GridHeight; y++)
                {


                    if (minHeight>mapHeights[x,y])
                    {
                        minHeight = mapHeights[x, y];
                        
                    }

                    if (maxHeight<mapHeights[x,y])
                    {

                        maxHeight = mapHeights[x, y];

                        
                    }




                }
            }
 
        }




        #endregion



    }


}
